Actions and Callbacks

An action is a function which is designed to handle an input event. An action therefore implements a particular input behavior exhibited by each instance of a certain contact class. In fact, an action function is typically a CLOS generic function, and actions usually are defined as methods of a particular contact class.

 
(defmethod blink ((contact blinker) blink-on-p)
  (with-slots (on-p) contact
    ;; Set internal state variable on/off
    (setf on-p blink-on-p)
    ;; Redisplay based on new on-p state
    (display contact)
    ;; Invoke callback with new state
    (apply-callback contact :blink on-p))

In general, an action represents a well-defined contact behavior that could be done in response to any event. In fact, it's possible to personalize an existing UI by modifying its event translations so that contact actions are rebound to the types of events (i.e. event specifications) which are more to your taste. In some cases, however, an action may be designed to handle a very specific event type.

When an action computes a result that is important to an application, it invokes a callback. This is done via the apply-callback macro. The example above shows that a blinker contact has a callback whose name is :blink, which is called with a single argument, i.e. a boolean representing the new on/off state of the blinker. What significance this has to the application is unknown to the contact programmer who wrote this action; it all depends on what callback function the application programmer has associated with :blink for this blinker instance. In fact, the application programmer may have decided not to define a callback function for :blink, in which case :blink has no application meaning at all (and apply-callback simply does nothing). As shown in the :blink example, apply-callback is usually (although not necessarily) called directly by an action method or somewhere within its dynamic extent.

\framebox[5.5in]{
\hspace*{\fill}
{\bf Note}
\hspace*{\fill}
\parbox[t]{4.5in}{
...
...ach {\tt
blinker} usually has different application semantics.} \hspace*{\fill}}

In the previous example, the blink action method does not depend on the event which causes it to be invoked. But what happens when an action's behavior depends on information contained in the event? CLUE handles this by representing an event as an instance of the event class. An event object has slots which contact various kind of interesting information about the event. Since there are many types of X events, there are many different event slots, even though for a given event some slots are irrelevant and therefore have a nil value. You can look in the X Protocol Specification for a complete description of event slots (and look in the CLX specification to find out how these slot values are represented in Lisp).

But how does an action access an event object? This is done by using a special CLUE macro called with-event. For example:

(defmethod beep ((contact blinker) &optional (per-cent-volume 0))
  (with-event (state)
    ;; Was the shift key down?
    (when (member :shift (make-state-keys state))
      ;; Ring server's chime!
      (bell (contact-display contact) per-cent-volume)  
      ;; Invoke callback
      (apply-callback contact :beep))))

The beep action will beep only if it determines that the shift key was down when the event occurred. It does this by examining the state slot of the event, which defines which modifier keys were pressed at the time of the event. Notice the use of the with-event macro. This is similar to the with-slots macro of CLOS. with-event binds slots of the ``current'' event argument within its lexical extent, so that the action code can refer to them. But why is the current event object hidden in this way? Because this allows for a much more efficient implementation of event objects than would be possible if their structure was fully exposed to programmers. So, there!